/*
 * DAQRun.cpp
 *
 *  Created on: May 17, 2012
 *      Author: dblyth
 */

#include <cmath>
#include <iostream>
#include <sstream>
using namespace std;

#include "DAQRun.h"

DAQRun::DAQRun() {
	registerListCount = 0;
	registerList = new configRegister*[registerListCount];

	ADCBufferLevel = 0.0;
	eventCount = 0;
	memBufferLevel = 0.0;
	runName = "";
	stopTime = -1;

	eventsInQueue = 0;
	queue = new unsigned char*[0x100000];

	pthread_mutex_init(&runLock, NULL);
	pthread_mutex_init(&infoLock, NULL);
	pthread_mutex_init(&queueLock, NULL);
	pthread_cond_init(&queueReady, NULL);
}

DAQRun::~DAQRun() {
	pthread_mutex_lock(&runLock);
	for (int i = 0; i < registerListCount; i++)
		delete registerList[i];
	delete[] registerList;
	registerListCount = 0;
	pthread_mutex_destroy(&runLock);

	pthread_mutex_lock(&infoLock);
	for (int i = 0; i < histoListCount; i++)
		delete histoList[i];
	delete[] histoList;
	histoListCount = 0;
	pthread_mutex_destroy(&infoLock);

	pthread_mutex_lock(&queueLock);
	for (int i = 0; i < eventsInQueue; i++)
		delete[] removeFromQueue();
	delete[] queue;
	pthread_mutex_destroy(&queueLock);
	pthread_cond_destroy(&queueReady);
}

void DAQRun::addConfigRegister(unsigned int address, unsigned int value, CVDataWidth dataWidth) {
	configRegister* reg = new configRegister;
	reg->address = address;
	reg->value = value;
	reg->dataWidth = dataWidth;
	pthread_mutex_lock(&runLock);
	addToRegisterList(reg);
	pthread_mutex_unlock(&runLock);
}

float DAQRun::getADCBufferLevel() {
	pthread_mutex_lock(&infoLock);
	float level = ADCBufferLevel;
	pthread_mutex_unlock(&infoLock);
	return level;
}

int DAQRun::getEventCount() {
	pthread_mutex_lock(&infoLock);
	int count = eventCount;
	pthread_mutex_unlock(&infoLock);
	return count;
}

TH1D* DAQRun::getHistoCopy(const char* name) {
	TH1D* histo = NULL;
	pthread_mutex_lock(&infoLock);
	for (int i = 0; i < histoListCount; i++)
		if (strcmp(histoList[i]->GetName(), name) == 0) {
			histo = (TH1D*) histoList[i]->Clone();
			break;
		}
	pthread_mutex_unlock(&infoLock);
	return histo;
}

int DAQRun::getHistoCount() {
	pthread_mutex_lock(&infoLock);
	int count = histoListCount;
	pthread_mutex_unlock(&infoLock);
	return count;
}

const char* DAQRun::getHistoName(int index) {
	pthread_mutex_lock(&infoLock);
	if (index >= histoListCount) {
		pthread_mutex_unlock(&infoLock);
		return "";
	}
	const char* name = histoList[index]->GetName();
	pthread_mutex_unlock(&infoLock);
	return name;
}

float DAQRun::getMemBufferLevel() {
	pthread_mutex_lock(&infoLock);
	float level = memBufferLevel;
	pthread_mutex_unlock(&infoLock);
	return level;
}

const char* DAQRun::getRunName() {
	pthread_mutex_lock(&infoLock);
	const char* name = runName.c_str();
	pthread_mutex_unlock(&infoLock);
	return name;
}

bool DAQRun::isRunning() {
	pthread_mutex_lock(&infoLock);
	bool isRunning = shouldRun;
	pthread_mutex_unlock(&infoLock);
	return isRunning;
}

void DAQRun::setADCBaseAddress(unsigned int ADCAddress) {
	pthread_mutex_lock(&runLock);
	ADCBaseAddr = ADCAddress;
	pthread_mutex_unlock(&runLock);
}

void DAQRun::setDuration(long duration) {
	pthread_mutex_lock(&runLock);
	stopTime = duration;
	pthread_mutex_unlock(&runLock);
}

void DAQRun::setNumPedSamples(int num) {
	pthread_mutex_lock(&runLock);
	numPedSamples = num;
	pthread_mutex_unlock(&runLock);
}

void DAQRun::setRunOff() {
	pthread_mutex_lock(&infoLock);
	shouldRun = false;
	pthread_mutex_unlock(&infoLock);
}

void* DAQRun::run(void* voidPtr) {
	DAQRun* run = (DAQRun*) voidPtr;
	run->doRun();

	pthread_exit(NULL);
	return NULL;
}

void DAQRun::doRun() {
	pthread_mutex_lock(&runLock);

	pthread_mutex_lock(&infoLock);
	shouldRun = true;
	pthread_mutex_unlock(&infoLock);

	int32_t bridgeHandle = 0;
	handleFlag(CAENVME_Init(cvV1718, 0, 0, &bridgeHandle));

//	int32_t digitizerHandle = 0;
//	handleFlag(CAENVME_Init(cvV2718, 0, 0, &digitizerHandle));

	unsigned int reset = 0;
	handleFlag(CAENVME_WriteCycle(bridgeHandle, ADCBaseAddr | 0xef24, &reset, cvA32_S_DATA, cvD32));

	for (int i = 0; i < registerListCount; i++) {
		handleFlag(
				CAENVME_WriteCycle(bridgeHandle, (unsigned int) (registerList[i]->address),
						(unsigned int*) &(registerList[i]->value), cvA32_S_DATA,
						(CVDataWidth) (registerList[i]->dataWidth)));
	}

	unsigned int customSize = 0;
	handleFlag(CAENVME_ReadCycle(bridgeHandle, ADCBaseAddr | 0x8020, &customSize, cvA32_S_DATA, cvD32));

	unsigned int bufferOrg = 0;
	handleFlag(CAENVME_ReadCycle(bridgeHandle, ADCBaseAddr | 0x800C, &bufferOrg, cvA32_S_DATA, cvD32));

	double numBufferSlots = pow((float) 2, (int) (0xf & bufferOrg));

	wordsPerChannel = 0;
	if (customSize == 0)
		wordsPerChannel = 0x100000 / numBufferSlots / 2.0;
	else
		wordsPerChannel = customSize * 2;

	enableMask = 0;
	handleFlag(CAENVME_ReadCycle(bridgeHandle, ADCBaseAddr | 0x8120, &enableMask, cvA32_S_DATA, cvD32));

	numEnabled = 0;
	for (int i = 0; i < 8; i++)
		if (enableMask & (1 << i))
			numEnabled++;

	unsigned int turnRunOn = 0x4;
	handleFlag(CAENVME_WriteCycle(bridgeHandle, ADCBaseAddr | 0x8100, &turnRunOn, cvA32_S_DATA, cvD32));

	if (stopTime > 0)
		stopTime += time(NULL);

	pthread_mutex_lock(&infoLock);
	shouldSaveData = true;
	pthread_mutex_unlock(&infoLock);
	pthread_create(&saveDataThread, NULL, &saveData, (void*) this);

	pthread_mutex_lock(&infoLock);
	int numCharsRequested = 4 * (wordsPerChannel * numEnabled + 4);
	while (shouldRun) {
		pthread_mutex_unlock(&infoLock);

		unsigned int numEventsInADC = 0;
		handleFlag(CAENVME_ReadCycle(bridgeHandle, ADCBaseAddr | 0x812c, &numEventsInADC, cvA32_S_DATA, cvD32));

		unsigned int status = 0;
		handleFlag(CAENVME_ReadCycle(bridgeHandle, ADCBaseAddr | 0x8104, &status, cvA32_S_DATA, cvD32));

		pthread_mutex_lock(&infoLock);
		ADCBufferLevel = numEventsInADC / numBufferSlots * 100.0;
		pthread_mutex_unlock(&infoLock);

		if (!(status & 0x8) || numEventsInADC < 10)
			usleep(10000);

		while ((status & 0x8) && numEventsInADC > 9) {
			int numCharsRetrieved = 0;

			if (numCharsRequested > 4000) {
				unsigned char* newEvent = new unsigned char[numCharsRequested];

				pthread_mutex_lock(&infoLock);
				int tempRetrieved = 0;
				while (numCharsRetrieved < numCharsRequested) {
					pthread_mutex_unlock(&infoLock);
					int tempRequested = 4000;
					if (tempRequested > numCharsRequested - numCharsRetrieved)
						tempRequested = numCharsRequested - numCharsRetrieved;
					handleFlag(
							CAENVME_BLTReadCycle(bridgeHandle, ADCBaseAddr | 0x0000, &newEvent[numCharsRetrieved],
									tempRequested, cvA32_S_BLT, cvD32, &tempRetrieved));
					numCharsRetrieved += tempRetrieved;
					pthread_mutex_lock(&infoLock);
				}
				pthread_mutex_unlock(&infoLock);

				numEventsInADC--;

				if (numCharsRetrieved == numCharsRequested) {
					pthread_mutex_lock(&queueLock);
					addToQueue(newEvent);
					pthread_cond_broadcast(&queueReady);
					pthread_mutex_unlock(&queueLock);
				} else
					delete[] newEvent;
			} else if (numCharsRequested <= 2000 && 4000 / numCharsRequested + 9 < numEventsInADC) {
				int groupCount = 4000 / numCharsRequested;
				unsigned char* newEvent = new unsigned char[numCharsRequested * groupCount];

				handleFlag(
						CAENVME_BLTReadCycle(bridgeHandle, ADCBaseAddr | 0x0000, newEvent,
								numCharsRequested * groupCount, cvA32_S_BLT, cvD32, &numCharsRetrieved));

				numEventsInADC -= groupCount;

				if (numCharsRetrieved == numCharsRequested * groupCount) {
					pthread_mutex_lock(&queueLock);
					for (int i = 0; i < groupCount; i++) {
						unsigned char* newNewEvent = new unsigned char[numCharsRequested];
						for (int j = 0; j < numCharsRequested; j++)
							newNewEvent[j] = newEvent[j + i * numCharsRequested];
						addToQueue(newNewEvent);
					}
					pthread_cond_broadcast(&queueReady);
					pthread_mutex_unlock(&queueLock);
				}

				delete[] newEvent;
			} else {
				unsigned char* newEvent = new unsigned char[numCharsRequested];

				handleFlag(
						CAENVME_BLTReadCycle(bridgeHandle, ADCBaseAddr | 0x0000, newEvent, numCharsRequested,
								cvA32_S_BLT, cvD32, &numCharsRetrieved));

				numEventsInADC--;

				if (numCharsRetrieved == numCharsRequested) {
					pthread_mutex_lock(&queueLock);
					addToQueue(newEvent);
					pthread_cond_broadcast(&queueReady);
					pthread_mutex_unlock(&queueLock);
				} else
					delete[] newEvent;
			}
		}

		pthread_mutex_lock(&infoLock);
		if (stopTime > 0 && time(NULL) > stopTime)
			shouldRun = false;
	}
	pthread_mutex_unlock(&infoLock);

	unsigned int turnRunOff = 0x0;
	CAENVME_WriteCycle(bridgeHandle, ADCBaseAddr | 0x8100, &turnRunOff, cvA32_S_DATA, cvD32);

	CAENVME_End(bridgeHandle);
//	CAENVME_End(digitizerHandle);

	pthread_mutex_lock(&infoLock);
	shouldSaveData = false;
	pthread_mutex_unlock(&infoLock);
	pthread_mutex_lock(&queueLock);
	pthread_cond_broadcast(&queueReady);
	pthread_mutex_unlock(&queueLock);
	pthread_join(saveDataThread, NULL);

	pthread_mutex_unlock(&runLock);
}

void* DAQRun::saveData(void* voidPtr) {
	DAQRun* run = (DAQRun*) voidPtr;
	run->doSaveData();

	pthread_exit(NULL);
	return NULL;
}

void DAQRun::doSaveData() {
	pthread_mutex_lock(&infoLock);
	const int samplesPerChannel = wordsPerChannel * 2;
	const int numEnabledLocal = numEnabled;

	if (numPedSamples > samplesPerChannel)
		numPedSamples = samplesPerChannel;

	histoListCount = numEnabled * 2 + 1;
	histoList = new TH1D*[histoListCount];

	histoList[0] = new TH1D("Combined spec", "Spectrum;integrated value (arb);frequency (arb)", 1100, -2000, 20000);

	int j = -1;
	for (int i = 0; i < numEnabled; i++) {
		for (j++; j < 8; j++) {
			if (enableMask & (1 << j))
				break;
		}

		stringstream histoNameSS;
		histoNameSS << "Chan " << j;
		histoList[i * 2 + 1] = new TH1D((histoNameSS.str() + " spec").c_str(),
				"Spectrum;integrated value (arb);frequency (arb)", 1100, -2000, 20000);
		histoList[(i + 1) * 2] = new TH1D((histoNameSS.str() + " pulse").c_str(), "Pulse;time (arb);ADC value (arb)",
				wordsPerChannel * 2, -0.5, wordsPerChannel * 2 - 0.5);
	}
	pthread_mutex_unlock(&infoLock);

	TFile* file = new TFile("data.root", "update");
	int index;
	for (index = 0; true; index++) {
		std::stringstream runTempSS;
		runTempSS << "run" << index;

		TTree* test = (TTree*) file->Get(runTempSS.str().c_str());
		if (test == NULL)
			break;
	}
	std::stringstream runSS;
	runSS << "run" << index;

	TTree* tree = new TTree(runSS.str().c_str(), runSS.str().c_str());
	tree->SetAutoSave(1000000);

	TBranch* maskBranch = tree->Branch("channelMask", &enableMask, "channelMask/i");
	pthread_mutex_lock(&infoLock);
	maskBranch->Fill();
	pthread_mutex_unlock(&infoLock);

	TBranch* samplesBranch = tree->Branch("samplesPerChannel", (void*) &samplesPerChannel, "samplesPerChannel/I");
	samplesBranch->Fill();

	const int charsPerChannel = samplesPerChannel * 2;

	unsigned short data[numEnabledLocal][charsPerChannel / 2];
	std::stringstream branchSS;
	branchSS << "data[" << numEnabledLocal << "][" << charsPerChannel / 2 << "]/s";
	TBranch* dataBranch = tree->Branch("data", &data, branchSS.str().c_str());

	int n = 0;

	pthread_mutex_lock(&infoLock);
	runName = runSS.str();
	eventCount = n;
	while (shouldSaveData) {
		pthread_mutex_unlock(&infoLock);

		pthread_mutex_lock(&queueLock);
		pthread_cond_wait(&queueReady, &queueLock);
		while (eventsInQueue > 0) {
			pthread_mutex_lock(&infoLock);
			memBufferLevel = eventsInQueue / 0x100000;
			pthread_mutex_unlock(&infoLock);

			unsigned char* event = removeFromQueue();
			pthread_mutex_unlock(&queueLock);

//			cout << hex << (int) (event[0] | (event[1] << 8) | (event[2] << 16) | (event[3] << 24)) << endl;

			for (int i = 0; i < samplesPerChannel; i++) {
				for (int j = 0; j < numEnabledLocal; j++) {
					int pos = i * 2 + j * charsPerChannel + 16;
					data[j][i] = (unsigned short) event[pos] + (unsigned short) ((event[pos + 1] & 0xf) << 8);
				}
			}

			dataBranch->Fill();
			n++;

			double sum = 0;
			pthread_mutex_lock(&infoLock);
			for (int i = 0; i < numEnabled; i++) {
				double ped = 0;
				int total = 0;
				for (int j = 0; j < samplesPerChannel; j++) {
					total += data[i][j];
					histoList[(i + 1) * 2]->SetBinContent(j + 1, data[i][j]);
					if (j < numPedSamples)
						ped += data[i][j];
				}
				ped *= ((double) samplesPerChannel) / numPedSamples;

				double tempSum = ped - total;
//				if (tempSum < 0)
//					continue;
				histoList[i * 2 + 1]->Fill(tempSum);

				sum += tempSum;
			}

//			if (sum >= 0)
			histoList[0]->Fill(sum);

			eventCount = n;
			pthread_mutex_unlock(&infoLock);

			delete[] event;

			pthread_mutex_lock(&queueLock);
		}
		pthread_mutex_unlock(&queueLock);

		pthread_mutex_lock(&infoLock);
		memBufferLevel = 0;
	}
	pthread_mutex_unlock(&infoLock);

	TBranch* numEventsBranch = tree->Branch("numEvents", &n, "numEvents/I");
	numEventsBranch->Fill();

	tree->AutoSave("SaveSelf");
	file->Close();
}

void DAQRun::addToRegisterList(configRegister* newRegister) {
	configRegister** newRegisterList = new configRegister*[registerListCount + 1];
	for (int i = 0; i < registerListCount; i++)
		newRegisterList[i] = registerList[i];
	delete[] registerList;
	registerList = newRegisterList;
	registerList[registerListCount] = newRegister;
	registerListCount++;
}

void DAQRun::addToQueue(unsigned char* newEvent) {
	if (eventsInQueue == 0x100000)
		delete[] newEvent;
	else {
		queue[eventsInQueue] = newEvent;
		eventsInQueue++;
	}
}

unsigned char* DAQRun::removeFromQueue() {
	eventsInQueue--;
	return queue[eventsInQueue];
}

void DAQRun::handleFlag(CVErrorCodes flag) {
	if (flag != cvSuccess) {
		pthread_mutex_lock(&infoLock);
		shouldRun = false;
		pthread_mutex_unlock(&infoLock);
		cout << getErrorString(flag) << endl;
	}
}

string DAQRun::getErrorString(CVErrorCodes error) {
	switch (error) {
	case cvSuccess:
		return "SUCCESS!";
	case cvBusError:
		return "BUS ERROR";
	case cvCommError:
		return "COMMUNICATION ERROR";
	case cvGenericError:
		return "GENERIC ERROR";
	case cvInvalidParam:
		return "INVALID PARAMETERS";
	case cvTimeoutError:
		return "TIMEOUT ERROR";
	}
}
